Programmet til I2C-Loconet Arduino





/*
 * Slave Loconet I/O handler
 *
 * Can be used by any I2C master (RasPi, BeagleBone...)
 *
 * Copyright (c) 2015 John Plocher, Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
 * 
 * Based on i2c/arduino info found in 
 *    http://playground.arduino.cc/Main/WireLibraryDetailedReference
 *    http://www.gammon.com.au/forum/?id=10896
 * as well as an embedded Loconet-compatible arduino shield/interface such as
 *    http://www.spcoast.com/wiki/index.php/LocoShield
 *
 *  This sketch is an interface between I2C and a LocoShield, allowing an external I2C master 
 *  to send and receive LocoNet packets without having to deploy a timing-constrained real time 
 *  hardware driver
 *
 *  The master writes either a 1-byte command or a 2-16 byte loconet packet to the slave.
 *  If a command is sent, the master follows up with a read of the information asked for. 
 *
 * Current status (2015-02-14):  I2C interactions are solid, Rx path seems to work, Tx path untested
 */
#define DEBUG

#include <elapsedMillis.h>

#include <LocoNet.h>
#define LNET_TX_PIN  7
#define LNET_RX_PIN  8

elapsedMillis et;

#include <Wire.h>
// defines BUFFER_LENGTH (default on UNO = 32) which is max i2c transfer allowed...
// Since LNet max packet size (in LNPE) is 16, this should be no problem.

#define INT_PIN     12      // tie to master to communicate "something ready to read"
#define INTERRUPT_ON  HIGH
#define INTERRUPT_OFF  LOW

#define SLAVE_ADDRESS 0x01

/*
 * Commands
 */
enum {
     GETAVAILABLE  = 0,   // ->  1 byte, Packet Length to read next (0=no packet to read_
     GETPACKET     = 1,   // -> variable (2-16), LNet packet with checksum
     GETERRORS     = 2,   // -> 10 bytes (see LocoErrors struct below)
     GETVERSION    = 3,   // ->  1 byte
     NUMCOMMANDS
};

static byte protocol_version = 0x01;

LnBuf        LnTxBuffer;
lnMsg        *LnTxPacket;
lnMsg        *LnRxPacket;

volatile struct LocoErrors {
    int RxPackets;    // 2 bytes each * 5 items = 10 bytes of data
    int RxErrors;
    int TxPackets;
    int TxErrors;
    int Collisions;
} errors;

volatile byte receivedCommand;  // sent by master, used to determine how to respond to reads

void sendavailable2master(void) {
    byte l = LocoNet.length();
    Wire.write(l);    
}

void sendpacket2master(void) {
    if (LocoNet.length()) {
        LnRxPacket = LocoNet.receive();
        Wire.write((const uint8_t *)LnRxPacket->data, getLnMsgSize( LnRxPacket ));
        LnRxPacket = NULL;
    }
}

void senderrors2master(void) {
    LnBufStats *stats = LocoNet.getStats(); // get and copy stats maintained by the Loconet subsystem
    errors.RxPackets  = stats->RxPackets;
    errors.RxErrors   = stats->RxErrors;
    errors.TxPackets  = stats->TxPackets;
    errors.TxErrors   = stats->TxErrors;
    errors.Collisions = stats->Collisions;
    Wire.write((const uint8_t *)&errors, sizeof (struct LocoErrors));
}

// callback from Wire - the master is reading from us, I2C conversation is in progress...
void requestEvent(){
    digitalWrite(INT_PIN, INTERRUPT_OFF);  // reset the INT, since someone is talking to us...
    
    switch (receivedCommand) {     
     case GETAVAILABLE:  sendavailable2master();         break;
     case GETPACKET:     sendpacket2master();            break;
     case GETERRORS:     senderrors2master();            break;
     case GETVERSION:    Wire.write(protocol_version);   break;
   }
}

// callback from Wire - the master is writing to us, I2C conversation is in progress...
void receiveEvent(int bytesReceived) {
    digitalWrite(INT_PIN, INTERRUPT_OFF);  // reset the INT, since someone is talking to us...
    if (bytesReceived == 1) {  // One byte means a command...
        receivedCommand = Wire.read();
        if (receivedCommand >= NUMCOMMANDS) receivedCommand = GETAVAILABLE;
    } else {                   // More than one byte sent must be a packet to send
        int validbytes = min(bytesReceived, BUFFER_LENGTH);
        for (int a = 0; a < validbytes; a++) {
            addByteLnBuf( &LnTxBuffer, Wire.read());  // grab at max 32 (on UNO...) bytes...
            // side effect:  when a whole, valid packet is in the tx buffer, the main loop() will send it
        }
        for (int a = validbytes; a < bytesReceived; a++) {
            Wire.read();  // if we receive more data then allowed just throw it away
                          // though the stack is probably already corrupted in Wire()...
        }
    }
}
#if defined(DEBUG)
void printW6(int x) {
    if (x < 10000) Serial.print(" ");
    if (x <  1000) Serial.print(" ");
    if (x <   100) Serial.print(" ");
    if (x <    10) Serial.print(" ");
    Serial.print(x, DEC);
    Serial.print(" ");
}
void printW4(int x) {
    if (x < 100) Serial.print(" ");
    if (x <  10) Serial.print(" ");
    Serial.print(x, DEC);
    Serial.print(" ");
}

int was = -1;  // for only-on-change debug printing in loop()
#endif

void setup() {

delay(10000);

#if defined(DEBUG)
    Serial.begin(115200);
    while (!Serial);
    Serial.println("LocoNet I2C Slave");
    et = 0;
    Serial.println("LoconetRX    wi  ri  rpi col len RXpkt RXerr TXpkt TXerr Colls"); 
#endif
    
    pinMode(13, OUTPUT);      // LED         
    pinMode(INT_PIN, OUTPUT);      // interrupt pin         
    digitalWrite(INT_PIN, INTERRUPT_OFF);
    
    pinMode(LNET_TX_PIN, OUTPUT);  // Loconet Send Data     (7)
    pinMode(LNET_RX_PIN, INPUT);   // Loconet RxD	    (8)
    LocoNet.init(LNET_TX_PIN);     // initialize the LocoNet interface
    initLnBuf(&LnTxBuffer);
    

    receivedCommand = GETAVAILABLE;
    Wire.begin(SLAVE_ADDRESS); 
    Wire.onRequest(requestEvent);
    Wire.onReceive(receiveEvent);
}


void loop() {
  
    // wait for incoming packets, tell master via an interrupt line...
    int a = LocoNet.available();
#if defined(DEBUG)
    if ((a != was) || (et > 10000)) {
        Serial.print( a ? "*Available*  " 
                        : " no traffic  ");
        printW4(LocoNet.LnBuffer.WriteIndex); 
        printW4(LocoNet.LnBuffer.ReadIndex);
        printW4(LocoNet.LnBuffer.ReadPacketIndex); 
        printW4(LocoNet.LnBuffer.CheckSum); 
        printW4(LocoNet.LnBuffer.ReadExpLen);
        
        LnBufStats *stats = LocoNet.getStats(); // get and copy stats maintained by the Loconet subsystem
        printW6(stats->RxPackets);
        printW6(stats->RxErrors);
        printW6(stats->TxPackets);
        printW6(stats->TxErrors);
        printW6(stats->Collisions);

        Serial.println();
        et = 0;
        was = a;
    }
#endif
    if ( a ) {
        digitalWrite(INT_PIN, INTERRUPT_ON);   // assert INT to master
        digitalWrite(13,      INTERRUPT_ON);   // echo it on onboard LED for debugging
    } else {
        digitalWrite(INT_PIN, INTERRUPT_OFF);  // nothing to see here...
        digitalWrite(13,      INTERRUPT_OFF);  // echo it on onboard LED for debugging
    }
    LnTxPacket = recvLnMsg( &LnTxBuffer );
    if (LnTxPacket) {
        LocoNet.send( (lnMsg*)LnTxPacket );
    }
}